/*-------------------------------------------------------------------------
 * slot.h
 *	   复制插槽管理。
 *
 * Copyright (c) 2012-2022, PostgreSQL Global Development Group
 *
 *-------------------------------------------------------------------------
 */
#ifndef SLOT_H
#define SLOT_H

#include "access/xlog.h"
#ifdef FDD
#include "replication/slot_xlog.h"
#else
#include "access/xlogreader.h"
#endif
#include "storage/condition_variable.h"
#include "storage/lwlock.h"
#include "storage/shmem.h"
#include "storage/spin.h"
#include "replication/walreceiver.h"

#ifndef FDD
/*
 * 复制槽的行为，在释放或崩溃时。
 *
 * 被标记为 PERSISTENT 的槽是崩溃安全的，
 * 并且在释放时不会被删除。被标记为 EPHEMERAL 的槽
 * 在释放时或重新启动后会被删除。被标记为 TEMPORARY 的槽
 * 会在会话结束时或发生错误时被删除。
 *
 * EPHEMERAL 用于创建持久性
 * 槽时的未准备状态。通过调用
 * ReplicationSlotPersist() 可以将 EPHEMERAL 槽
 * 变为 PERSISTENT。对于在会话结束时消失的槽，
 * TEMPORARY 是合适的选择。
 */
typedef enum ReplicationSlotPersistency
{
	RS_PERSISTENT,
	RS_EPHEMERAL,
	RS_TEMPORARY
} ReplicationSlotPersistency;

/*
 * 跨重启保留的复制槽的磁盘数据。
 */
typedef struct ReplicationSlotPersistentData
{
	/* 槽的标识符 */
	NameData	name;

	/* 数据库当前激活的插槽 */
	Oid			database;

	/*
	 * 插槽被丢弃（或在崩溃后恢复）时的行为。
	 */
	ReplicationSlotPersistency persistency;

	/*
	 * 数据的xmin范围
	 *
	 * 注意：这可能代表一个尚未写入磁盘的值；
	 * 请参见下面的effective_xmin注释。
	 */
	TransactionId xmin;

	/*
	 * 目录元组的xmin范围
	 *
	 * 注意：这可能代表一个尚未写入磁盘的值；
	 * 请参见下面的effective_xmin注释。
	 */
	TransactionId catalog_xmin;

	/* 可能被此复制插槽要求的最早的LSN */
	XLogRecPtr	restart_lsn;

	/* 当插槽失效时，这里会复制restart_lsn */
	XLogRecPtr	invalidated_at;

	/*
	 * 客户端已确认接收的最早LSN。这被用作
	 * start_lsn 点，以防客户端未指定一个，同时也作为
	 * 安全措施，以防客户端指定的
	 * start_lsn早于这个值。
	 */
	XLogRecPtr	confirmed_flush;

	/*
	 * 我们为此插槽启用了两阶段提交时的LSN或
	 * 在插槽创建时找到一致性点的LSN。
	 */
	XLogRecPtr	two_phase_at;

	/*
	 * 允许解码已准备的事务吗？
	 */
	bool		two_phase;

	/* 插件名称 */
	NameData	plugin;
} ReplicationSlotPersistentData;
#endif


/*
 * 单个复制插槽的共享内存状态。
 *
 * 复制插槽的内存数据遵循基于两个链接概念的锁定模型：
 * - 复制插槽的in_use标志在添加或丢弃时通过
 * LWLock ReplicationSlotControlLock进行切换，更新标志时需要
 * 在独占模式下由拥有插槽的后端持有，而读取者（不拥有插槽的并发后端）
 * 在查看复制插槽数据时需要以共享模式持有它。
 * - 各个字段由互斥锁保护，只有拥有
 * 插槽的后端被授权从其自己的插槽更新字段。
 * 拥有插槽的后端在读取自己的字段时不需要获得此锁，而不拥有
 * 此插槽的并发后端在读取该插槽数据时应获得锁。
 */
typedef struct ReplicationSlot
{
	/* 锁，与effective_xmin在同一缓存行上 */
	slock_t		mutex;

	/* 这个插槽是否已定义 */
	bool		in_use;

	/* 谁在为这个插槽流出更改？ 使用中的插槽为0。 */
	pid_t		active_pid;

	/* 是否有未完成的修改？ */
	bool		just_dirtied;
	bool		dirty;

	/*
	 * 对于逻辑解码来说，极其重要的是我们绝不能移除
	 * 任何仍然需要用于解码的数据，即使在崩溃后；
	 * 否则，解码将产生错误的结果。普通的流复制
	 * 还需要防止旧行版本被过早移除，
	 * 但我们可能遇到的最严重后果是
	 * 在备用上产生不必要的查询取消。因此，对于逻辑
	 * 解码，这个值代表已实际写入磁盘的最新xmin，
	 * 而对于流复制，它与持久值（data.xmin）是一样的。
	 */
	TransactionId effective_xmin;
	TransactionId effective_catalog_xmin;

	/* 在关闭和崩溃中生存的数据 */
	ReplicationSlotPersistentData data;

	/* 是否有人在此插槽上执行IO？ */
	LWLock		io_in_progress_lock;

	/* 当active_pid变化时信号条件变量 */
	ConditionVariable active_cv;

	/* 剩余的所有数据仅用于逻辑插槽 */

	/*
	 * 当客户端确认flushes >= candidate_xmin_lsn时，我们可以
	 * 前进catalog xmin。当restart_valid已通过时，
	 * restart_lsn可以增加。
	 */
	TransactionId candidate_catalog_xmin;
	XLogRecPtr	candidate_xmin_lsn;
	XLogRecPtr	candidate_restart_valid;
	XLogRecPtr	candidate_restart_lsn;
} ReplicationSlot;

#define SlotIsPhysical(slot) ((slot)->data.database == InvalidOid)
#define SlotIsLogical(slot) ((slot)->data.database != InvalidOid)

/*
 * 所有复制插槽的共享内存控制区域。
 */
typedef struct ReplicationSlotCtlData
{
	/*
	 * 这个数组应该声明为[FLEXIBLE_ARRAY_MEMBER]，但因某种
	 * 原因在一个空结构体中不能这样做。
	 */
	ReplicationSlot replication_slots[1];
} ReplicationSlotCtlData;

/*
 * 指向共享内存的指针
 */
extern PGDLLIMPORT ReplicationSlotCtlData *ReplicationSlotCtl;
extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;

/* GUCs */
extern PGDLLIMPORT int max_replication_slots;

/* shmem初始化函数 */
extern Size ReplicationSlotsShmemSize(void);
extern void ReplicationSlotsShmemInit(void);

/* 单个插槽的管理 */
extern void ReplicationSlotCreate(const char *name, bool db_specific,
#ifdef FDD
								  ReplicationSlotPersistency p, bool two_phase, bool failover);
#else
								  ReplicationSlotPersistency p, bool two_phase);
#endif
extern void ReplicationSlotPersist(void);
extern void ReplicationSlotDrop(const char *name, bool nowait);

extern void ReplicationSlotAcquire(const char *name, bool nowait);
extern void ReplicationSlotRelease(void);
extern void ReplicationSlotCleanup(void);
extern void ReplicationSlotSave(void);
extern void ReplicationSlotMarkDirty(void);

/* 杂项内容 */
extern void ReplicationSlotInitialize(void);
extern bool ReplicationSlotValidateName(const char *name, int elevel);
extern void ReplicationSlotReserveWal(void);
extern void ReplicationSlotsComputeRequiredXmin(bool already_locked);
extern void ReplicationSlotsComputeRequiredLSN(void);
#ifdef FDD
extern void ReplicationSlotAcquiredSetFailover(bool failover);
extern XLogRecPtr GetReplicationSlotsComputeRequiredLSN(bool failover_only);
#endif
extern XLogRecPtr ReplicationSlotsComputeLogicalRestartLSN(void);
extern bool ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive);
extern void ReplicationSlotsDropDBSlots(Oid dboid);
extern bool InvalidateObsoleteReplicationSlots(XLogSegNo oldestSegno);
extern ReplicationSlot *SearchNamedReplicationSlot(const char *name, bool need_lock);
extern int	ReplicationSlotIndex(ReplicationSlot *slot);
extern bool ReplicationSlotName(int index, Name name);
extern void ReplicationSlotNameForTablesync(Oid suboid, Oid relid, char *syncslotname, int szslot);
extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok);

#ifdef FDD
extern void StartupReplicationSlots(bool drop_nonfailover_slots);
#else
extern void StartupReplicationSlots(void);
#endif
extern void CheckPointReplicationSlots(void);

extern void CheckSlotRequirements(void);
extern void CheckSlotPermissions(void);

#endif							/* SLOT_H */
